package cn.octuc.ccu;


import android.support.v7.app.AppCompatActivity;

import android.Manifest;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import com.alibaba.fastjson.JSON;

import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.AudioSource;
import org.webrtc.AudioTrack;
import org.webrtc.DataChannel;
import org.webrtc.EglBase;
import org.webrtc.IceCandidate;
import org.webrtc.Logging;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.MediaStreamTrack;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.RendererCommon;
import org.webrtc.RtpReceiver;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
import org.webrtc.SurfaceTextureHelper;
import org.webrtc.SurfaceViewRenderer;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoFrame;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;


import pub.devrel.easypermissions.EasyPermissions;


public class MainActivity extends AppCompatActivity {
    static String TAG = "ccu";

    CUConnection cuConnection = new CUConnection();

    public static final String VIDEO_TRACK_ID = "1";//"ARDAMSv0";
    public static final String AUDIO_TRACK_ID = "2";//"ARDAMSa0";

    private static final int VIDEO_RESOLUTION_WIDTH = 1280;
    private static final int VIDEO_RESOLUTION_HEIGHT = 720;
    private static final int VIDEO_FPS = 30;

    //OpenGL ES
    private EglBase mRootEglBase;
    //纹理渲染
    private SurfaceTextureHelper mSurfaceTextureHelper;

    //继承自 surface view
    private SurfaceViewRenderer mLocalSurfaceView;
    private SurfaceViewRenderer mRemoteSurfaceView;
    private TextView mLogcatView;

    //用于数据传输
    private PeerConnection mPeerConnection;
    private PeerConnectionFactory mPeerConnectionFactory;

    private VideoTrack mVideoTrack;
    private AudioTrack mAudioTrack;

    private VideoCapturer mVideoCapture;
    CUWebSocket cuWebsocket = new CUWebSocket();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        this.cuConnection.Test();

        String[] perms = {Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO};
        if (!EasyPermissions.hasPermissions(this, perms)) {
            EasyPermissions.requestPermissions(this, "Need permissions for camera & microphone", 0, perms);
        }


        mLogcatView = findViewById(R.id.LogcatView);
        mLocalSurfaceView = findViewById(R.id.LocalSurfaceView);
        mRemoteSurfaceView = findViewById(R.id.RemoteSurfaceView);

        mLogcatView.setMovementMethod(ScrollingMovementMethod.getInstance());
        mRemoteSurfaceView.setOnLongClickListener((View v) -> {
            mLogcatView.setText("");
            return true;
        });

        mRootEglBase = EglBase.create();


        mLocalSurfaceView.init(mRootEglBase.getEglBaseContext(), null);
        mLocalSurfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
        //mLocalSurfaceView.setMirror(false);
        mLocalSurfaceView.setEnableHardwareScaler(false /* enabled */);

        mRemoteSurfaceView.init(mRootEglBase.getEglBaseContext(), null);
        mRemoteSurfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
        //mRemoteSurfaceView.setMirror(false);
        mRemoteSurfaceView.setEnableHardwareScaler(true /* enabled */);
        mRemoteSurfaceView.setZOrderMediaOverlay(true);

        //创建 factory， pc是从factory里获得的
        mPeerConnectionFactory = this.cuConnection.createPeerConnectionFactory(this, mRootEglBase.getEglBaseContext());

        // NOTE: this _must_ happen while PeerConnectionFactory is alive!
        Logging.enableLogToDebugOutput(Logging.Severity.LS_VERBOSE);

        mVideoCapture = this.cuConnection.createVideoCapturer(this);

        mSurfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", mRootEglBase.getEglBaseContext());
        VideoSource videoSource = mPeerConnectionFactory.createVideoSource(false);
        mVideoCapture.initialize(mSurfaceTextureHelper, this.getApplicationContext(), videoSource.getCapturerObserver());


        mVideoTrack = mPeerConnectionFactory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
        mVideoTrack.setEnabled(true);
        mVideoTrack.addSink(mLocalSurfaceView);
        mVideoTrack.addSink((VideoFrame videoFrame) -> {
            // videoFrame
        });

        AudioSource audioSource = mPeerConnectionFactory.createAudioSource(new MediaConstraints());
        mAudioTrack = mPeerConnectionFactory.createAudioTrack(AUDIO_TRACK_ID, audioSource);
        mAudioTrack.setEnabled(true);

        // 自动连接 WebSocket
        connectWebSocket();
    }

    @Override
    protected void onResume() {
        super.onResume();

        mVideoCapture.startCapture(VIDEO_RESOLUTION_WIDTH, VIDEO_RESOLUTION_HEIGHT, VIDEO_FPS);
    }

    @Override
    protected void onPause() {
        super.onPause();

        try {
            mVideoCapture.stopCapture();
        } catch (InterruptedException e) {
            e.printStackTrace();
            Log.e(TAG, "onPause: " + e.getMessage());
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        mLocalSurfaceView.release();
        mRemoteSurfaceView.release();
        mVideoCapture.dispose();
        mSurfaceTextureHelper.dispose();
        PeerConnectionFactory.stopInternalTracingCapture();
        PeerConnectionFactory.shutdownInternalTracer();
        mPeerConnectionFactory.dispose();
    }


    private void logcatOnUI(String msg) {
        Log.e(TAG, msg);
        runOnUiThread(() -> {
            mLogcatView.append("\n" + msg);

            int offset = mLogcatView.getLineCount() *mLogcatView.getLineHeight();
            //如果文字段落高度大于可显示高度，则滑动到末尾（+20是为了让末尾不贴边）
            if(offset -mLogcatView.getHeight() >0) {
                mLogcatView.scrollTo(0, offset - mLogcatView.getHeight() + 20);
            }
        });
    }

    public void doStartCall() {
        logcatOnUI("start call, wait...");

        if (mPeerConnection == null) {
            mPeerConnection = this.cuConnection.createPeerConnection(mPeerConnectionObserver, mVideoTrack, mAudioTrack);
        }

        MediaConstraints mediaConstraints = new MediaConstraints();
        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
        mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
        mPeerConnection.createOffer(new SimpleSdpObserver("[createOffer]") {
            @Override
            public void onCreateSuccess(SessionDescription sessionDescription) {
                super.onCreateSuccess(sessionDescription);

                mPeerConnection.setLocalDescription(new SimpleSdpObserver("[createOffer.setLocalDescription]"), sessionDescription);

                logcatOnUI("setLocalDescription success");
                JSONObject message = new JSONObject();
                try {
                    message.put("type", "offer");
                    message.put("sdp", sessionDescription.description);
                    // 发送消息
                } catch (JSONException e) {
                    e.printStackTrace();
                    logcatOnUI("==" + e.getMessage());
                }
            }
        }, mediaConstraints);

    }


    public void doAnswerCall() {
        if (mPeerConnection == null) {
            mPeerConnection = this.cuConnection.createPeerConnection(mPeerConnectionObserver, mVideoTrack, mAudioTrack);
        }

        MediaConstraints sdpMediaConstraints = new MediaConstraints();
        logcatOnUI("Create answer ...");
        mPeerConnection.createAnswer(new SimpleSdpObserver("[createAnswer]") {
            @Override
            public void onCreateSuccess(SessionDescription sessionDescription) {
                logcatOnUI("Create answer success !");
                mPeerConnection.setLocalDescription(new SimpleSdpObserver("[createAnswer.setLocalDescription]"),
                        sessionDescription);

                PdAnswer payload = new PdAnswer();
                payload.type = "answer";
                payload.data = new PdSdp();
                payload.data.type = "answer";
                payload.data.sdp = sessionDescription.description;

                WsClientMsg msg = new WsClientMsg();
                msg.type = "relayMessage";
                msg.code = 0;
                msg.msg = "success";

                msg.payload = payload;

                String data = JSON.toJSONString(msg);
                cuWebsocket.sendString(data);

            }
        }, sdpMediaConstraints);
    }


    private PeerConnection.Observer mPeerConnectionObserver = new PeerConnection.Observer() {
        @Override
        public void onSignalingChange(PeerConnection.SignalingState signalingState) {
            Log.i(TAG, "onSignalingChange: " + signalingState);
        }

        @Override
        public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
            Log.i(TAG, "onIceConnectionChange: " + iceConnectionState);
        }

        @Override
        public void onIceConnectionReceivingChange(boolean b) {
            Log.i(TAG, "onIceConnectionChange: " + b);
        }

        @Override
        public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
            Log.i(TAG, "onIceGatheringChange: " + iceGatheringState);
        }

        @Override
        public void onIceCandidate(IceCandidate iceCandidate) {
            logcatOnUI("onIceCandidate: " + iceCandidate.sdp);

            PdCandiData payload = new PdCandiData();
            payload.type = "candidata";
            payload.candidate = iceCandidate.sdp;
            payload.id = iceCandidate.sdpMid;
            payload.label = iceCandidate.sdpMLineIndex;

            WsClientMsg msg = new WsClientMsg();
            msg.type = "relayMessage";
            msg.code = 0;
            msg.msg = "success";
            msg.payload = payload;

            String data = JSON.toJSONString(msg);
            cuWebsocket.sendString(data);
            // 发送消息
        }

        @Override
        public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
            for (int i = 0; i < iceCandidates.length; i++) {
                Log.i(TAG, "onIceCandidatesRemoved: " + iceCandidates[i]);
            }
            mPeerConnection.removeIceCandidates(iceCandidates);
        }

        @Override
        public void onAddStream(MediaStream mediaStream) {
            Log.i(TAG, "onAddStream: " + mediaStream.videoTracks.size());
        }

        @Override
        public void onRemoveStream(MediaStream mediaStream) {
            Log.i(TAG, "onRemoveStream");
        }

        @Override
        public void onDataChannel(DataChannel dataChannel) {
            Log.i(TAG, "onDataChannel");
        }

        @Override
        public void onRenegotiationNeeded() {
            Log.i(TAG, "onRenegotiationNeeded");
        }

        @Override
        public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
            logcatOnUI("onAddTrack");
            MediaStreamTrack track = rtpReceiver.track();
            if (track instanceof VideoTrack) {
                Log.i(TAG, "onAddVideoTrack");
                VideoTrack remoteVideoTrack = (VideoTrack) track;
                remoteVideoTrack.setEnabled(true);
                remoteVideoTrack.addSink(mRemoteSurfaceView);
            }
        }
    };


    public static class SimpleSdpObserver implements SdpObserver {
        String mName = " ";

        public SimpleSdpObserver(String name) {
            this.mName = name + " ";
        }

        @Override
        public void onSetSuccess() {
            Log.e(TAG, mName + "onSetSuccess: ~");
        }

        @Override
        public void onCreateSuccess(SessionDescription sessionDescription) {
            Log.e(TAG, mName + "onCreateSuccess success:");
            //Log.e(TAG, "onCreateSuccess success: \n" + sessionDescription.description);
        }

        @Override
        public void onCreateFailure(String s) {
            Log.e(TAG, mName + "onCreateFailure: " + s);
        }

        @Override
        public void onSetFailure(String s) {
            Log.e(TAG, mName + "onSetFailure: " + s);
        }
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }


    static class WsClientMsg {
        public int code;
        public String msg;
        public String type;
        public Object payload;
    }

    static class PdPayload {
        public String type;
    }

    static class PdSdp {
        public String type;
        public String sdp;
    }

    static class PdOffer {
        public String type;
        public PdSdp data;
    }

    static class PdCandiData {
        public String type;
        public int label;
        public String id;
        public String candidate;
    }

    static class PdAnswer {
        public String type;
        public PdSdp data;
    }

    void onRecvWebSocketMessage(String data) {
        try {
            WsClientMsg msg = JSON.parseObject(data, WsClientMsg.class);
            if (!msg.type.equals("relayMessage")) {
                return;
            }

            String payload = JSON.toJSONString(msg.payload);
            PdPayload pay = JSON.parseObject(payload, PdPayload.class);

            logcatOnUI("recv msg: " + pay.type);

            if (pay.type.equals("offer")) {
                PdOffer offer = JSON.parseObject(payload, PdOffer.class);
                Log.e(TAG, "onRecvWebSocketMessage: sdp: " + offer.data.sdp);

                mPeerConnection.setRemoteDescription(
                        new SimpleSdpObserver("[setRemoteDescription]"),
                        new SessionDescription(
                                SessionDescription.Type.OFFER,
                                offer.data.sdp));

                this.doAnswerCall();

            } else if (pay.type.equals("candidata")) {
                PdCandiData candidata = JSON.parseObject(payload, PdCandiData.class);
                Log.e(TAG, "onRecvWebSocketMessage: candidata:" + candidata.candidate);
                IceCandidate remoteIceCandidate = new IceCandidate(candidata.id, candidata.label, candidata.candidate);

                logcatOnUI("recv candidata: " + candidata.candidate);
                mPeerConnection.addIceCandidate(remoteIceCandidate);
            }


        } catch (Exception e) {
            logcatOnUI(e.getMessage());
        }
    }


    // 连接 WebSocket
    void connectWebSocket() {

        CUWebSocketListener listener = new CUWebSocketListener() {
            @Override
            public void onOpen() {
                logcatOnUI("WebSocket open");
            }

            @Override
            public void onClose() {
                logcatOnUI("WebSocket close");
            }

            @Override
            public void onError(String err) {
                logcatOnUI("WebSocket error: " + err);
            }

            @Override
            public void onMessage(String msg) {
                onRecvWebSocketMessage(msg);
            }
        };

        String url = "wss://www.octuc.cn:1443/ccu/api/v1/device/ws"; // "ws://echo.websocket.org";
        cuWebsocket.connectWebSocket(url, listener);

        if (mPeerConnection == null) {
            mPeerConnection = this.cuConnection.createPeerConnection(mPeerConnectionObserver, mVideoTrack, mAudioTrack);
        }
    }
}
